﻿using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;

namespace LightCAD.Core
{
    [JsonConverter(typeof(Vector2dConverter))]
    public struct Vector2d
    {
        [JsonInclude]
        public double X;

        [JsonInclude]
        public double Y;

        //[JsonInclude]
        //public double Data;//用于存储扩展数据例如地形图的Z或其他

        public Vector2d() { }

        public Vector2d(double x, double y)
        {
            this.X = x;
            this.Y = y;
        }
        //public Vector2d(double x, double y, double data)
        //{
        //    this.X = x;
        //    this.Y = y;
        //    this.Data = data;
        //}

        public void Set(double? newX, double? newY, double? newData)
        {
            if (newX != null) this.X = newX.Value;
            if (newY != null) this.Y = newY.Value;
            //if (newData != null) this.Data = newData.Value;
        }

        public override string ToString()
        {
            // return string.Format("Vector2({0}, {1})", this.x, this.y);
            return string.Format("{0:f6}, {1:f6}", this.X, this.Y);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Vector2d))
            {
                return false;
            }

            return this.Equals((Vector2d)obj);
        }

        public bool Equals(Vector2d rhs)
        {
            return (Utils.IsEqual(X, rhs.X) && Utils.IsEqual(Y, rhs.Y));
        }

        public override int GetHashCode()
        {
            return X.GetHashCode() ^ Y.GetHashCode();
        }

        [JsonIgnore]
        public double Length
        {
            get
            {
                return Math.Sqrt((this.X * this.X) + (this.Y * this.Y));
            }
        }

        [JsonIgnore]
        public double LengthSqrd
        {
            get
            {
                return ((this.X * this.X) + (this.Y * this.Y));
            }
        }

        public void Normalize()
        {
            double length = this.Length;
            if (length != 0.0)
            {
                this.X /= length;
                this.Y /= length;
            }
        }
        [JsonIgnore]
        public Vector2d Normalized
        {
            get
            {
                double length = this.Length;
                if (length != 0.0)
                {
                    return new Vector2d(this.X / length, this.Y / length);
                }
                return this;
            }
        }
        /// <summary>
        /// Obtains the angle of a vector.
        /// </summary>
        /// <param name="u">A Vector2.</param>
        /// <returns>Angle in radians.</returns>
        public double Angle()
        {
            double angle = Math.Atan2(this.Y, this.X);
            if (angle < 0)
            {
                return Utils.TwoPI + angle;
            }

            return angle;
        }


        public Vector2d Lerp(Vector2d that, double t)
        {
            double omt = 1.0 - t;
            return new Vector2d(
                this.X * omt + that.X * t,
                this.Y * omt + that.Y * t
            );
        }
        /**
     *  min
     *
     *  @param {module:kld-affine.Point2D} that
     *  @returns {number}
     */
        public Vector2d Min(Vector2d that)
        {
            return new Vector2d(
                Math.Min(this.X, that.X),
                Math.Min(this.Y, that.Y)
            );
        }

        /**
         *  max
         *
         *  @param {module:kld-affine.Point2D} that
         *  @returns {number}
         */
        public Vector2d Max(Vector2d that)
        {
            return new Vector2d(
                Math.Max(this.X, that.X),
                Math.Max(this.Y, that.Y)
            );
        }

        public static Vector2d UnitX { get; } = new Vector2d(1, 0);
        public static Vector2d UnitY { get; } = new Vector2d(0, 1);
        public static Vector2d Zero { get; } = new Vector2d(0, 0);
        public static Vector2d One { get; } = new Vector2d(1, 1);

        public void Copy(Vector2d from)
        {
            this.X = from.X;
            this.Y = from.Y;
            //this.Data= from.Data;
        }
        
        public Vector2d Clone()
        {
            return new Vector2d(this.X, this.Y);
        }
        public bool Similarity(Vector2d a)
        {
           if(a==this)
                return true;
           else if(Vector2d.Distance(this,a)<1)
                return true;
           else return false;

        }
        

        public static double Dot(Vector2d a, Vector2d b)
        {
            return a.X * b.X + a.Y * b.Y;
        }

        public static double Cross(Vector2d a, Vector2d b)
        {
            return ((a.X * b.Y) - (a.Y * b.X));
        }

        ///// <summary>
        ///// Returns the unsigned angle in degrees between a and b.
        ///// The smaller of the two possible angles between the two vectors is used.
        ///// The result value range: [0, 180]
        ///// </summary>
        //public static double Angle(Vector2 a, Vector2 b)
        //{
        //    return Utils.RadianToDegree(AngleInRadian(a, b));
        //}


        /// <summary>
        /// Obtains the angle of a line defined by two points.
        /// </summary>
        /// <param name="u">A Vector2.</param>
        /// <param name="v">A Vector2.</param>
        /// <returns>Angle in radians.</returns>
        public static double Angle(Vector2d u, Vector2d v)
        {
            Vector2d dir = v - u;
            return dir.Angle();
        }


        /// <summary>
        /// Returns the unsigned angle in radians between a and b.
        /// The smaller of the two possible angles between the two vectors is used.
        /// The result value range: [0, PI]
        /// </summary>
        public static double AngleInRadian(Vector2d a, Vector2d b)
        {
            double num = a.Length * b.Length;
            if (num == 0.0)
            {
                return 0.0;
            }
            double num2 = Dot(a, b) / num;
            return Math.Acos(Utils.Clamp(num2, -1.0, 1.0));
        }

        /// <summary>
        /// Returns the signed acute clockwise angle in degrees between from and to.
        /// The result value range: [-180, 180]
        /// </summary>
        public static double SignedAngle(Vector2d from, Vector2d to)
        {
            return Utils.RadianToDegree(SignedAngleInRadian(from, to));
        }

        /// <summary>
        /// Returns the signed acute clockwise angle in radians between from and to.
        /// The result value range: [-PI, PI]
        /// </summary>
        public static double SignedAngleInRadian(Vector2d from, Vector2d to)
        {
            double rad = AngleInRadian(from, to);
            if (Cross(from, to) < 0)
            {
                rad = -rad;
            }
            return rad;
        }

        public static double Distance(Vector2d a, Vector2d b)
        {
            Vector2d vector = b - a;
            return vector.Length;
        }

        public static double AnglesDifference(double firstAngle, double secondAngle)
        {
            double difference = secondAngle - firstAngle;
            while (difference < -Utils.PI) difference += Utils.TwoPI;
            while (difference > Utils.PI) difference -= Utils.TwoPI;

            return difference;
        }


        // Evaluates if the points are clockwise.
        public static bool Clockwise(Vector2d p1, Vector2d p2, Vector2d p3)
        {
            return ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 1e-8;
        }

        public static Vector2d Polar(Vector2d u, double distance, double angle)
        {
            Vector2d dir = new Vector2d(Math.Cos(angle), Math.Sin(angle));
            return u + dir * distance;
        }

        public static Vector2d Rotate(Vector2d v, double angle)
        {
            return RotateInRadian(v, Utils.DegreeToRadian(angle));
        }

        public static Vector2d Rotate(Vector2d point, Vector2d basePoint, double angle)
        {
            return RotateInRadian(point, basePoint, Utils.DegreeToRadian(angle));
        }

        public static Vector2d RotateInRadian(Vector2d v, double rad)
        {
            double x = v.X * Math.Cos(rad) - v.Y * Math.Sin(rad);
            double y = v.X * Math.Sin(rad) + v.Y * Math.Cos(rad);
            return new Vector2d(x, y);
        }

        /// <summary>
        /// Rotates one point around another TM
        /// </summary>
        /// <param name="pointToRotate">The point to rotate.</param>
        /// <param name="centerPoint">The center point of rotation.</param>
        /// <param name="angleInDegrees">The rotation angle in degrees.</param>
        /// <returns>Rotated point</returns>
        public static Vector2d RotateInRadian(Vector2d pointToRotate, Vector2d centerPoint, double angleInRadians)
        {
            double cosTheta = Math.Cos(angleInRadians);
            double sinTheta = Math.Sin(angleInRadians);
            return new Vector2d
            {
                X = (cosTheta * (pointToRotate.X - centerPoint.X) - sinTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.X),
                Y = (sinTheta * (pointToRotate.X - centerPoint.X) + cosTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.Y)
            };
        }

        public static Vector2d PointOrthoMode(Vector2d last, Vector2d point, bool ortho)
        {
            if (ortho)
            {
                if (Math.Abs(point.X - last.X) > Math.Abs(point.Y - last.Y))
                    return new Vector2d(point.X, last.Y);
                else
                    return new Vector2d(last.X, point.Y);
            }
            else
            {
                return point;
            }


        }

        public static Vector2d operator +(Vector2d a, Vector2d b)
        {
            return new Vector2d(a.X + b.X, a.Y + b.Y);
        }

        public static Vector2d operator -(Vector2d a, Vector2d b)
        {
            return new Vector2d(a.X - b.X, a.Y - b.Y);
        }

        public static Vector2d operator -(Vector2d a)
        {
            return new Vector2d(-a.X, -a.Y);
        }

        public static Vector2d operator *(Vector2d a, double d)
        {
            return new Vector2d(a.X * d, a.Y * d);
        }

        public static Vector2d operator *(double d, Vector2d a)
        {
            return new Vector2d(a.X * d, a.Y * d);
        }

        public static Vector2d operator /(Vector2d a, double d)
        {
            return new Vector2d(a.X / d, a.Y / d);
        }

        public static bool operator ==(Vector2d lhs, Vector2d rhs)
        {
            return lhs.Equals(rhs);
        }

        public static bool operator !=(Vector2d lhs, Vector2d rhs)
        {
            return !(lhs == rhs);
        }
        public static Vector2d operator *(Vector2d left, Vector2d right)
        {
            return new Vector2d(
                left.X * right.X,
                left.Y * right.Y
            );
        }

    }
}
